#! /usr/bin/env python
# -*- coding: utf-8 -*-

import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *

# =============================================================================
# Filter through command
# =============================================================================
class FilterTrhouhgCommandDialog(QDialog):
    '''
    Executes commands based on a dialgo taking optionaly what the user typed
    in the document.
    '''
    def __init__(self, parent = None):
        QDialog.__init__(self, parent)
        self.setWindowTitle('Filter through command')
        self.setup_ui()
        self.background_processes = []
        QMetaObject.connectSlotsByName(self)
        
    def setup_ui(self):
        
        main_layout = QVBoxLayout()
        self.setLayout(main_layout)
        
        
        self.commandGroup = QGroupBox(self)
        self.commandGroup.setTitle('C&ommand')
        self.comboCommand = QComboBox(self)
        self.comboCommand.setObjectName('comboCommand')
        self.comboCommand.setStyleSheet('padding: 4px;')
        self.comboCommand.setEditable(True)
        self.comboCommand.setMinimumWidth(300)
        command_layout = QVBoxLayout()
        command_layout.addWidget(self.comboCommand)
        self.commandGroup.setLayout(command_layout)
        
        
        main_layout.addWidget(self.commandGroup)
        
        options_layout = QHBoxLayout()
        main_layout.addLayout(options_layout)
        
        self.optionGroupInput = QGroupBox(self)
        self.optionGroupInput.setTitle('Input')
        optionInputLayout = QVBoxLayout()
        self.optionGroupInput.setLayout(optionInputLayout)
        
        
        self.optionGroupOutput = QGroupBox(self)
        self.optionGroupOutput.setTitle('Output')
        optionOutputLayout = QVBoxLayout()
        self.optionGroupOutput.setLayout(optionOutputLayout)
        
        options_layout.addWidget(self.optionGroupInput)
        options_layout.addWidget(self.optionGroupOutput)
        
        # =====================================================================
        # Input
        # =====================================================================
        
        # No selection as input
        self.radioInputNone = QRadioButton(self.optionGroupInput)
        self.radioInputNone.setObjectName('radioInputNone')
        self.radioInputNone.setText('N&one')
        optionInputLayout.addWidget(self.radioInputNone)
        
        # Selection as input
        self.radioInputSelection = QRadioButton(self.optionGroupInput)
        self.radioInputSelection.setObjectName('radioInputSelection')
        self.radioInputSelection.setText('S&election')
        optionInputLayout.addWidget(self.radioInputSelection)
        
        
        
        # Whole document
        self.radioInputDocument = QRadioButton(self.optionGroupInput)
        self.radioInputDocument.setObjectName('radioInputDocument')
        self.radioInputDocument.setText('D&ocument')
        optionInputLayout.addWidget(self.radioInputDocument)
        
        spacerItem = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)
        optionInputLayout.addItem(spacerItem)

        # =====================================================================
        # Output
        # =====================================================================
        
        self.radioOutputDiscard = QRadioButton(self.optionGroupOutput)
        self.radioOutputDiscard.setObjectName('radioOutputDiscard')
        self.radioOutputDiscard.setText('&Discard')
        optionOutputLayout.addWidget(self.radioOutputDiscard)
        # replace selection, replace document, insert as text, insert as snippet
        # show as HTML, show as tooltip, create a new document
        self.radioOutputReplaceSelection = QRadioButton(self.optionGroupOutput)
        self.radioOutputReplaceSelection.setObjectName('radioOutputReplaceSelection')
        self.radioOutputReplaceSelection.setText('Replace &Selection')
        optionOutputLayout.addWidget(self.radioOutputReplaceSelection)
        
        self.radioOutputReplaceDocument = QRadioButton(self.optionGroupOutput)
        self.radioOutputReplaceDocument.setObjectName('radioOutputReplaceDocument')
        self.radioOutputReplaceDocument.setText('Replace &Document')
        optionOutputLayout.addWidget(self.radioOutputReplaceDocument)
        
        self.radioOutputInsertAsText = QRadioButton(self.optionGroupOutput)
        self.radioOutputInsertAsText.setObjectName('radioOutputInsertAsText')
        self.radioOutputInsertAsText.setText('Insert As &Text')
        optionOutputLayout.addWidget(self.radioOutputInsertAsText)
        
        self.radioOutputInsertAsSinppet = QRadioButton(self.optionGroupOutput)
        self.radioOutputInsertAsSinppet.setObjectName('radioOutputInsertAsSinppet')
        self.radioOutputInsertAsSinppet.setText('Insert As S&nippet')
        optionOutputLayout.addWidget(self.radioOutputInsertAsSinppet)
        
        self.radioOutputShowAsHTML = QRadioButton(self.optionGroupOutput)
        self.radioOutputShowAsHTML.setObjectName('radioOutputShowAsHTML')
        self.radioOutputShowAsHTML.setText('Show As &HTML')
        optionOutputLayout.addWidget(self.radioOutputShowAsHTML)
        
        self.radioOutputShowAsToolTip = QRadioButton(self.optionGroupOutput)
        self.radioOutputShowAsToolTip.setObjectName('radioOutputShowAsToolTip')
        self.radioOutputShowAsToolTip.setText('Show As Toolti&p')
        optionOutputLayout.addWidget(self.radioOutputShowAsToolTip)
        
        self.radioOutputCreateNewDocument = QRadioButton(self.optionGroupOutput)
        self.radioOutputCreateNewDocument.setObjectName('radioOutputCreateNewDocument')
        self.radioOutputCreateNewDocument.setText('Create New Do&cument')
        optionOutputLayout.addWidget(self.radioOutputCreateNewDocument)
        
        spacerItem = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)
        optionOutputLayout.addItem(spacerItem)
        
        # =====================================================================
        # Defaults
        # =====================================================================
        self.radioInputSelection.setChecked(True)
        self.radioOutputReplaceSelection.setChecked(True)
        
        self.buttonBox = QDialogButtonBox(self)
        self.buttonBox.setObjectName('buttonBox')
        self.buttonBox.setOrientation(Qt.Horizontal)
        self.buttonBox.setStandardButtons(QDialogButtonBox.Cancel)
        self.buttonExecute = QPushButton(self.buttonBox)
        self.buttonExecute.setText('Execute')
        self.buttonBox.addButton(self.buttonExecute, QDialogButtonBox.AcceptRole)
        
        main_layout.addWidget(self.buttonBox)
        
        self.connect(self.buttonBox, SIGNAL('accepted()'), self.accept)
        self.connect(self.buttonBox, SIGNAL('rejected()'), self.reject)
    
        is_radioButton = lambda e: isinstance(e, QRadioButton)
        self.inputRadios  = filter( is_radioButton, 
                                        self.optionGroupInput.children())
        self.outputRadios = filter( is_radioButton, 
                                        self.optionGroupOutput.children())
    
    def input_method():
        doc = 'Returns checkbox instance, sets checkbox based on its objectName'
        def fget(self):
            return filter(lambda e: e.isChecked(), self.inputRadios)[0]
        
        def fset(self, name):
            try:
                els = filter( lambda e: e.objectName() == name, self.inputRadios)
                els[0].setChecked(True)
            except IndexError:
                pass
        return locals()
    input_method = property(**input_method())
    
    def output_method():
        doc = 'Returns checkbox instance, sets checkbox based on its objectName'
        def fget(self):
            return filter(lambda e: e.isChecked(), self.outputRadios)[0]
        
        def fset(self, name):
            try:
                els = filter( lambda e: e.objectName() == name, self.outputRadios)
                els[0].setChecked(True)
            except IndexError:
                print "Coudn't find", name
                pass
        return locals()
    output_method = property(**output_method())
    NEED_DOCUMENT = (
        # Input
        'radioInputSelection', 'radioInputDocument',
        # Output
        'radioOutputReplaceSelection', 'radioOutputReplaceDocument',
        'radioOutputInsertAsText', 'radioOutputInsertAsSinppet',
        'radioOutputShowAsToolTip',
    )
    
    NEED_SELECTION = (
        # Input
        'radioInputSelection',
        # Output
        'radioOutputReplaceSelection'
    )
    def exec_(self):
        '''
        Enable or diasble availabe actions depending on the availability
        of a document in the tabPane
        '''
        tabPane = self.parent().centralWidget()
        if not tabPane.count():
            # Disable unavailable radioButtons
            map(lambda n: getattr(self, n).setEnabled(False), self.NEED_DOCUMENT)
            
            
            if not self.input_method.isEnabled():
                self.input_method = 'radioInputNone'

            if not self.output_method.isEnabled():
                self.output_method = 'radioOutputCreateNewDocument'
        else:
            # At least one document open
            map(lambda n: getattr(self, n).setEnabled(True), self.NEED_DOCUMENT)
            
            # Do we have selection?
            code_editor = tabPane.currentWidget()
            if code_editor.has_selection:
                map(lambda n: getattr(self, n).setEnabled(True), self.NEED_SELECTION)
                self.input_method = 'radioInputSelection'
                self.input_method = 'radioOutputReplaceSelection'
                
            else:
                map(lambda n: getattr(self, n).setEnabled(False), self.NEED_SELECTION)
                if not self.input_method.isEnabled():
                    self.input_method = 'radioInputDocument'
                
                if not self.output_method.isEnabled():
                    self.output_method = 'radioOutputCreateNewDocument'
            

        return QDialog.exec_(self)
    
    NEED_GRAB_DOCUMENT = ('radioInputDocument', 'radioOutputReplaceSelection',
                            'radioOutputInsertAsText',
                        )
    
    def accept(self):
        doc = None
        if self.input_method.objectName() in self.NEED_GRAB_DOCUMENT or\
            self.output_method.objectName() in self.NEED_GRAB_DOCUMENT:
            tabPane = self.parent().centralWidget()
            if tabPane.count():
                code_editor = tabPane.currentWidget()
                doc = code_editor.editor.document()
        
        cmd = self.comboCommand.currentText()
        if not cmd:
            return QDialog.accept(self)
        proc = BackgroundProcess(self, cmd, self.input_method.objectName(), 
                                            self.output_method.objectName(), doc)
        self.connect(proc, SIGNAL('proccessCompleted()'), self.cleanUp)
        self.background_processes.append(proc)
        return QDialog.accept(self)
        
    def cleanUp(self):
        # Cleaning references so that finished processes' memory is freed
        finished = []
        for proc in self.background_processes:
            if proc.state() == QProcess.NotRunning:
                finished.append(proc)
        map(lambda e: self.background_processes.remove(e), finished )
        print "Cleaned:", len(finished)


class BackgroundProcess(QProcess):
    '''
    Executes the background process.
    '''
    def __init__(self, parent, full_cmd, input_method, output_method, document):
        QProcess.__init__(self, parent)
        
        self.input_method = input_method
        self.output_method = output_method
        self.document = document
        
        self.timer = QTimer(self)
        self.connect(self.timer, SIGNAL('timeout()'), self.timeout_elapsed)
        self.full_cmd = full_cmd
        # This is simple way of donng things, isn't it
        args = str(full_cmd).split()
        cmd = args.pop(0)
        # TODO: Find out whichone is the best
        #self.connect(self, SIGNAL('readyRead()'), self.read_stdout)
        self.connect(self, SIGNAL('readyReadStandardOutput()'), self.read_stdout)
        self.connect(self, SIGNAL('readyReadStdout'), self.read_stdout)
        self.connect(self, SIGNAL('readyReadStandardError()'), self.read_error)
        self.connect(self, SIGNAL('started()'), self.proc_started)
        self.connect(self, SIGNAL('stateChanged(int)'), self.state_changed )
        self.connect(self, SIGNAL('error()'), self.proc_error)
        self.connect(self, SIGNAL('finished(int, QProcess::ExitStatus)'), self.proc_finished)
        
        cmd = self.get_platform_shell(full_cmd)
        #print "BackgroundProcess start >>", cmd
        self.start( cmd )
        # Write something?
        if self.input_method != 'radioInputNone':
            
            code_editor =  self.parent().parent().centralWidget().currentWidget()
            
            if self.input_method == 'radioInputSelection':
                data = str(code_editor.editor.textCursor().selectedText())
            elif self.input_method == 'radioInputDocument':
                data = str(code_editor.editor.toPlainText())
                
            self.write(data)
        self.closeWriteChannel()
        
        #if self.waitForStarted(4000000):
            #print "Finished", self.waitForFinished()
        #else:
            #print "Error"
    
    def get_platform_shell(self, cmd):
        if 'linux' in sys.platform:
            return 'sh -c \"%s\"' % cmd
        else:
            raise NotImplementedException('Not defined for %s' % sys.platform)
        
    # TODO: Maybe let the user edit this value
    TIMEOUT = 5000
    
    def timeout_elapsed(self):
        print "Timeout, attemping to terminate..."
        self.terminate()
    
    def proc_started(self):
        print "Started: ", self.full_cmd
        self.timer.start(self.TIMEOUT)
        
    def state_changed(self, state):
        print "State changed", state
    
    def read_stdout(self):
        # Restart the timer, the preocess is still alive
        self.timer.start(self.TIMEOUT)
        if self.output_method == 'radioOutputDiscard':
            self.readAllStandardOutput()
        
        
    def read_error(self):
        # Save the stderr to insert it in the file
        self.timer.start(self.TIMEOUT)
        
    def proc_error(self):
        print "Error in", self.full_cmd
        
    def proc_finished(self, exit_status, exit_code):
        self.timer.stop()
        code_edior = self.parent().parent().centralWidget().currentWidget()
        output = str(self.readAllStandardOutput())
        error = str(self.readAllStandardError())
        
        if self.output_method == 'radioOutputReplaceSelection':
            
            cursor = code_edior.editor.textCursor()
            cursor.clearSelection()
            #cursor.movePosition(QTextCursor.NextWord, QTextCursor.KeepAnchor)
            cursor.insertText(output)
            if not output:
                cursor.insertText(error)
            
        elif self.output_method == 'radioOutputReplaceDocument':
            pass
        elif self.output_method == 'radioOutputInsertAsText':
            cursor = code_edior.editor.textCursor()
            cursor.insertText(output)
            if not output:
                cursor.insertText(error)
                
        elif self.output_method == 'radioOutputInsertAsSinppet':
            pass
        elif self.output_method == 'radioOutputShowAsHTML':
            pass
        elif self.output_method == 'radioOutputShowAsToolTip':
            pass
        elif self.output_method == 'radioOutputCreateNewDocument':
            # Some navigation... :S
            tabPane = self.parent().parent().centralWidget()
            code_editor = tabPane.insert_new()
            cursor = code_editor.editor.textCursor()
            cursor.insertText(output)
            if not output:
                cursor.insertText(error)
                
        print "Finished,  status: %d code: %d" % (exit_status, exit_code)
        self.emit(SIGNAL('proccessCompleted()') )
        
